{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline\n",
    "from nettack import utils, GCN\n",
    "from nettack import nettack as ntk\n",
    "import numpy as np\n",
    "gpu_id = None # set this to your desired GPU ID if you want to use GPU computations (only for the GCN/surrogate training)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load network, basic setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Selecting 1 largest connected components\n"
     ]
    }
   ],
   "source": [
    "_A_obs, _X_obs, _z_obs = utils.load_npz('data/citeseer.npz')\n",
    "_A_obs = _A_obs + _A_obs.T\n",
    "_A_obs[_A_obs > 1] = 1\n",
    "lcc = utils.largest_connected_components(_A_obs)\n",
    "\n",
    "_A_obs = _A_obs[lcc][:,lcc]\n",
    "\n",
    "assert np.abs(_A_obs - _A_obs.T).sum() == 0, \"Input graph is not symmetric\"\n",
    "assert _A_obs.max() == 1 and len(np.unique(_A_obs[_A_obs.nonzero()].A1)) == 1, \"Graph must be unweighted\"\n",
    "assert _A_obs.sum(0).A1.min() > 0, \"Graph contains singleton nodes\"\n",
    "\n",
    "_X_obs = _X_obs[lcc].astype('float32')\n",
    "_z_obs = _z_obs[lcc]\n",
    "_N = _A_obs.shape[0]\n",
    "_K = _z_obs.max()+1\n",
    "_Z_obs = np.eye(_K)[_z_obs]\n",
    "_An = utils.preprocess_graph(_A_obs)\n",
    "sizes = [16, _K]\n",
    "degrees = _A_obs.sum(0).A1\n",
    "\n",
    "seed = 15\n",
    "unlabeled_share = 0.8\n",
    "val_share = 0.1\n",
    "train_share = 1 - unlabeled_share - val_share\n",
    "np.random.seed(seed)\n",
    "\n",
    "split_train, split_val, split_unlabeled = utils.train_val_test_split_tabular(np.arange(_N),\n",
    "                                                                       train_size=train_share,\n",
    "                                                                       val_size=val_share,\n",
    "                                                                       test_size=unlabeled_share,\n",
    "                                                                       stratify=_z_obs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Choose the node to attack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "u = 0 # node to attack\n",
    "assert u in split_unlabeled"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train surrogate model (i.e. GCN without nonlinear activation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 34 iterations\n"
     ]
    }
   ],
   "source": [
    "surrogate_model = GCN.GCN(sizes, _An, _X_obs, with_relu=False, name=\"surrogate\", gpu_id=gpu_id)\n",
    "surrogate_model.train(split_train, split_val, _Z_obs)\n",
    "W1 =surrogate_model.W1.eval(session=surrogate_model.session)\n",
    "W2 =surrogate_model.W2.eval(session=surrogate_model.session)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setup Nettack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "nettack = ntk.Nettack(_A_obs, _X_obs, _z_obs, W1, W2, u)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_attack = True\n",
    "n_influencers = 1 if direct_attack else 5\n",
    "n_perturbations = int(degrees[u]) # How many perturbations to perform. Default: Degree of the node\n",
    "perturb_features = True\n",
    "perturb_structure = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Poison the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "##### Starting attack #####\n",
      "##### Attack node with ID 0 using structure and feature perturbations #####\n",
      "##### Attacking the node directly #####\n",
      "##### Performing 12 perturbations #####\n",
      "##### ...1/12 perturbations ... #####\n",
      "Edge perturbation: [  0 666]\n",
      "##### ...2/12 perturbations ... #####\n",
      "Edge perturbation: [  0 496]\n",
      "##### ...3/12 perturbations ... #####\n",
      "Edge perturbation: [  0 258]\n",
      "##### ...4/12 perturbations ... #####\n",
      "Edge perturbation: [   0 2012]\n",
      "##### ...5/12 perturbations ... #####\n",
      "Edge perturbation: [  0 939]\n",
      "##### ...6/12 perturbations ... #####\n",
      "Edge perturbation: [  0 664]\n",
      "##### ...7/12 perturbations ... #####\n",
      "Edge perturbation: [  0 786]\n",
      "##### ...8/12 perturbations ... #####\n",
      "Edge perturbation: [  0 831]\n",
      "##### ...9/12 perturbations ... #####\n",
      "Edge perturbation: [   0 1437]\n",
      "##### ...10/12 perturbations ... #####\n",
      "Edge perturbation: [   0 1436]\n",
      "##### ...11/12 perturbations ... #####\n",
      "Edge perturbation: [   0 1063]\n",
      "##### ...12/12 perturbations ... #####\n",
      "Edge perturbation: [   0 1136]\n"
     ]
    }
   ],
   "source": [
    "nettack.reset()\n",
    "nettack.attack_surrogate(n_perturbations, perturb_structure=perturb_structure, perturb_features=perturb_features, direct=direct_attack, n_influencers=n_influencers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0, 666),\n",
       " (0, 496),\n",
       " (0, 258),\n",
       " (0, 2012),\n",
       " (0, 939),\n",
       " (0, 664),\n",
       " (0, 786),\n",
       " (0, 831),\n",
       " (0, 1437),\n",
       " (0, 1436),\n",
       " (0, 1063),\n",
       " (0, 1136)]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(nettack.structure_perturbations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(), (), (), (), (), (), (), (), (), (), (), ()]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(nettack.feature_perturbations)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train GCN without perturbations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "retrain_iters=5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "... 1/5 \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 32 iterations\n",
      "... 2/5 \n",
      "converged after 42 iterations\n",
      "... 3/5 \n",
      "converged after 36 iterations\n",
      "... 4/5 \n",
      "converged after 63 iterations\n",
      "... 5/5 \n",
      "converged after 41 iterations\n"
     ]
    }
   ],
   "source": [
    "classification_margins_clean = []\n",
    "class_distrs_clean = []\n",
    "gcn_before = GCN.GCN(sizes, _An, _X_obs, \"gcn_orig\", gpu_id=gpu_id)\n",
    "for _ in range(retrain_iters):\n",
    "    print(\"... {}/{} \".format(_+1, retrain_iters))\n",
    "    gcn_before.train(split_train, split_val, _Z_obs)\n",
    "    probs_before_attack = gcn_before.predictions.eval(session=gcn_before.session,feed_dict={gcn_before.node_ids: [nettack.u]})[0]\n",
    "    class_distrs_clean.append(probs_before_attack)\n",
    "    best_second_class_before = (probs_before_attack - 1000*_Z_obs[nettack.u]).argmax()\n",
    "    margin_before = probs_before_attack[_z_obs[nettack.u]] - probs_before_attack[best_second_class_before]\n",
    "    classification_margins_clean.append(margin_before)\n",
    "class_distrs_clean = np.array(class_distrs_clean)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train GCN with perturbations\n",
    "(insert your favorite node classification algorithm here)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "... 1/5 \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 43 iterations\n",
      "... 2/5 \n",
      "converged after 38 iterations\n",
      "... 3/5 \n",
      "converged after 39 iterations\n",
      "... 4/5 \n",
      "converged after 44 iterations\n",
      "... 5/5 \n",
      "converged after 50 iterations\n"
     ]
    }
   ],
   "source": [
    "classification_margins_corrupted = []\n",
    "class_distrs_retrain = []\n",
    "gcn_retrain = GCN.GCN(sizes, nettack.adj_preprocessed, nettack.X_obs.tocsr(), \"gcn_retrain\", gpu_id=gpu_id)\n",
    "for _ in range(retrain_iters):\n",
    "    print(\"... {}/{} \".format(_+1, retrain_iters))\n",
    "    gcn_retrain.train(split_train, split_val, _Z_obs)\n",
    "    probs_after_attack = gcn_retrain.predictions.eval(session=gcn_retrain.session,feed_dict={gcn_retrain.node_ids: [nettack.u]})[0]\n",
    "    best_second_class_after = (probs_after_attack - 1000*_Z_obs[nettack.u]).argmax()\n",
    "    margin_after = probs_after_attack[_z_obs[nettack.u]] - probs_after_attack[best_second_class_after]\n",
    "    class_distrs_retrain.append(probs_after_attack)\n",
    "    classification_margins_corrupted.append(margin_after)\n",
    "class_distrs_retrain = np.array(class_distrs_retrain)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualize results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f54f7aaab70>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def make_xlabel(ix, correct):\n",
    "    if ix==correct:\n",
    "        return \"Class {}\\n(correct)\".format(ix)\n",
    "    return \"Class {}\".format(ix)\n",
    "\n",
    "figure = plt.figure(figsize=(12,4))\n",
    "plt.subplot(1, 2, 1)\n",
    "center_ixs_clean = []\n",
    "for ix, block in enumerate(class_distrs_clean.T):\n",
    "    x_ixs= np.arange(len(block)) + ix*(len(block)+2)\n",
    "    center_ixs_clean.append(np.mean(x_ixs))\n",
    "    color = '#555555'\n",
    "    if ix == nettack.label_u:\n",
    "        color = 'darkgreen'\n",
    "    plt.bar(x_ixs, block, color=color)\n",
    "\n",
    "ax=plt.gca()\n",
    "plt.ylim((-.05, 1.05))\n",
    "plt.ylabel(\"Predicted probability\")\n",
    "ax.set_xticks(center_ixs_clean)\n",
    "ax.set_xticklabels([make_xlabel(k, nettack.label_u) for k in range(_K)])\n",
    "ax.set_title(\"Predicted class probabilities for node {} on clean data\\n({} re-trainings)\".format(nettack.u, n_perturbations, retrain_iters))\n",
    "\n",
    "fig = plt.subplot(1, 2, 2)\n",
    "center_ixs_retrain = []\n",
    "for ix, block in enumerate(class_distrs_retrain.T):\n",
    "    x_ixs= np.arange(len(block)) + ix*(len(block)+2)\n",
    "    center_ixs_retrain.append(np.mean(x_ixs))\n",
    "    color = '#555555'\n",
    "    if ix == nettack.label_u:\n",
    "        color = 'darkgreen'\n",
    "    plt.bar(x_ixs, block, color=color)\n",
    "\n",
    "\n",
    "ax=plt.gca()\n",
    "plt.ylim((-.05, 1.05))\n",
    "ax.set_xticks(center_ixs_retrain)\n",
    "ax.set_xticklabels([make_xlabel(k, nettack.label_u) for k in range(_K)])\n",
    "ax.set_title(\"Predicted class probabilities for node {} after {} perturbations\\n({} re-trainings)\".format(nettack.u, n_perturbations, retrain_iters))\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:tf]",
   "language": "python",
   "name": "conda-env-tf-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
